namespace SimpleWebServices.Net
{
    using System;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Net.Mime;
    using System.Web;
    using SimpleWebServices.Configuration;
    using SimpleWebServices.Web;
    using SimpleWebServices.Xml;

    public sealed class HttpExpectations : ITestHttp, IRequestAcceptContent, IRequestAcceptLanguage, IRequestCookie, IRequestMethod, IResponseStatus, IResponseCacheControl, IResponseCacheConditionals, IResponseContentLanguage, IResponseContentMD5, IRequestContent, IResponseHtml, IResponseXml
    {
        private HttpRequestDefinition _request;
        private IHttpContent _content;
        private Response _response;

        public HttpExpectations(AbsoluteUri location)
        {
            this.Items = new Collection<ITestHttpExpectation>();
            this._request = new HttpRequestDefinition(location);
        }

        string ITestHttp.Location
        {
            get
            {
                return (this as ITestHttp).Result ? this._response.Headers[HttpResponseHeader.Location] : null as string;
            }
        }

        bool ITestHttp.Result
        {
            get
            {
                return 0 == this.Items.Where(x => !x.Check(this._response)).Count();
            }
        }

        private Collection<ITestHttpExpectation> Items
        {
            get;
            set;
        }

        public static IRequestAcceptContent New(AbsoluteUri location)
        {
            return new HttpExpectations(location);
        }

        IRequestAcceptLanguage IRequestAcceptContent.AcceptAnyContent()
        {
            return (this as IRequestAcceptContent).Accept("*/*");
        }

        IRequestAcceptLanguage IRequestAcceptContent.Accept(string type)
        {
            if (null == type)
            {
                throw new ArgumentNullException("type");
            }

            this._request.Headers["Accept"] = type;
            return this;
        }

        IRequestCookie IRequestAcceptLanguage.AcceptAnyLanguage()
        {
            return this;
        }

        IRequestCookie IRequestAcceptLanguage.AcceptLanguage(CultureInfo language)
        {
            if (null == language)
            {
                throw new ArgumentNullException("language");
            }

            return (this as IRequestAcceptLanguage).AcceptLanguage(language.Name);
        }

        IRequestCookie IRequestAcceptLanguage.AcceptLanguage(string language)
        {
            this._request.Headers["Accept-Language"] = language ?? string.Empty;
            return this;
        }

        IRequestMethod IRequestCookie.Cookies(CookieContainer container)
        {
            if (null == container)
            {
                throw new ArgumentNullException("container");
            }

            this._request.Cookies = container;

            return this;
        }

        IRequestMethod IRequestCookie.Cookieless()
        {
            return this;
        }

        IResponseStatus IRequestMethod.Delete()
        {
            return (this as IRequestMethod).Use("DELETE");
        }

        IResponseStatus IRequestMethod.Get()
        {
            return (this as IRequestMethod).Use("GET");
        }

        IResponseStatus IRequestMethod.Get(bool head)
        {
            return (this as IRequestMethod).Use("GET", head);
        }

        IResponseStatus IRequestMethod.Head()
        {
            return (this as IRequestMethod).Use("HEAD");
        }

        IResponseStatus IRequestMethod.Options()
        {
            return (this as IRequestMethod).Use("OPTIONS");
        }

        IResponseStatus IRequestMethod.Post(IHttpContent content)
        {
            return (this as IRequestMethod).Use("POST", content);
        }

        IResponseStatus IRequestMethod.Put(IHttpContent content)
        {
            return (this as IRequestMethod).Use("PUT", content);
        }

        IResponseStatus IRequestMethod.Use(string method)
        {
            return (this as IRequestMethod).Use(method, true);
        }

        IResponseStatus IRequestMethod.Use(string method, bool head)
        {
            if (null == method)
            {
                throw new ArgumentNullException("method");
            }
            else if (0 == method.Length)
            {
                throw new ArgumentOutOfRangeException("method");
            }

            this._request.Method = method;
            if (head && "GET".Equals(method, StringComparison.OrdinalIgnoreCase))
            {
                this.Items.Add(new HttpHeadTest(this._request));
            }

            return this;
        }

        IResponseStatus IRequestMethod.Use(string method, IHttpContent content)
        {
            if (null == content)
            {
                throw new ArgumentNullException("content");
            }
            else if ("GET".Equals(method, StringComparison.OrdinalIgnoreCase)
                || "HEAD".Equals(method, StringComparison.OrdinalIgnoreCase)
                || "OPTIONS".Equals(method, StringComparison.OrdinalIgnoreCase)
                || "TRACE".Equals(method, StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentOutOfRangeException("method");
            }

            (this as IRequestMethod).Use(method);
            this._content = content;
            return this;
        }

        IResponseCacheControl IResponseStatus.Is(HttpStatusCode status)
        {
            return (this as IResponseStatus).Is(status, false);
        }

        IResponseCacheControl IResponseStatus.Is(HttpStatusCode status, bool hasVersionHeaders)
        {
            if (!hasVersionHeaders && 5 < Environment.OSVersion.Version.Major)
            {
                foreach (string absent in TestingConfiguration.Settings().Http.Headers.Absent)
                {
                    this.Items.Add(new HttpResponseVersionTest(absent));
                }
            }

            this.Items.Add(new HttpStatusCodeTest(status));
            return this;
        }

        IResponseContentLanguage IResponseCacheControl.HasCacheControlNone()
        {
            return (this as IResponseCacheControl).HasCacheControl("no-cache") as IResponseContentLanguage;
        }

        IResponseCacheConditionals IResponseCacheControl.HasCacheControlPrivate()
        {
            return (this as IResponseCacheControl).HasCacheControl("private");
        }

        IResponseCacheConditionals IResponseCacheControl.HasCacheControlPublic()
        {
            return (this as IResponseCacheControl).HasCacheControl("public");
        }

        IResponseCacheConditionals IResponseCacheControl.HasCacheControl(string value)
        {
            this.Items.Add(new HttpResponseCacheControlTest(value));
            return this;
        }

        IResponseContentLanguage IResponseCacheControl.IgnoreCacheControl()
        {
            return this;
        }

        IResponseContentLanguage IResponseCacheConditionals.WithEtag()
        {
            this.Items.Add(new HttpResponseHeaderTest(HttpResponseHeader.ETag));
            return this;
        }

        IResponseContentLanguage IResponseCacheConditionals.WithExpires()
        {
            this.Items.Add(new HttpResponseHeaderTest(HttpResponseHeader.Expires));
            return this;
        }

        IResponseContentLanguage IResponseCacheConditionals.WithLastModified()
        {
            this.Items.Add(new HttpResponseHeaderTest(HttpResponseHeader.LastModified));
            return this;
        }

        IResponseContentLanguage IResponseCacheConditionals.IgnoreCacheConditionals()
        {
            return this;
        }

        IResponseContentMD5 IResponseContentLanguage.HasContentLanguageOfEnglish()
        {
            return (this as IResponseContentLanguage).HasContentLanguage("en");
        }

        IResponseContentMD5 IResponseContentLanguage.HasContentLanguage(CultureInfo language)
        {
            return (this as IResponseContentLanguage).HasContentLanguage(null == language ? string.Empty : language.Name);
        }

        IResponseContentMD5 IResponseContentLanguage.HasContentLanguage(string language)
        {
            this.Items.Add(new HttpResponseContentLanguageTest(language));
            return this;
        }

        IResponseContentMD5 IResponseContentLanguage.IgnoreContentLanguage()
        {
            return this;
        }

        IRequestContent IResponseContentMD5.HasContentMD5()
        {
            this.Items.Add(new HttpResponseContentMD5Test());
            return this;
        }

        IRequestContent IResponseContentMD5.IgnoreContentMD5()
        {
            return this;
        }

        ITestHttp IResponseStatus.IsContentNegotiationToTextHtml()
        {
            return (this as IResponseStatus).IsContentNegotiation("html");
        }

        ITestHttp IResponseStatus.IsContentNegotiation(string extension)
        {
            return (this as IResponseStatus).IsSeeOther(this.AppendToPath("." + extension));
        }

        ITestHttp IResponseStatus.IsLanguageNegotiationToEnglish()
        {
            return (this as IResponseStatus).IsLanguageNegotiation("en");
        }

        ITestHttp IResponseStatus.IsLanguageNegotiation(CultureInfo language)
        {
            return (this as IResponseStatus).IsLanguageNegotiation(null == language ? string.Empty : language.Name);
        }

        ITestHttp IResponseStatus.IsLanguageNegotiation(string language)
        {
            return (this as IResponseStatus).IsSeeOther(this.AppendToPath("." + language));
        }

        ITestHttp IResponseStatus.IsSeeOther(AbsoluteUri location)
        {
            if (null == location)
            {
                throw new ArgumentOutOfRangeException("location");
            }

            this.Items.Add(new HttpResponseLocationTest(location));
            return (this as IResponseStatus)
                .Is(HttpStatusCode.SeeOther)
                .HasCacheControlNone()
                .IgnoreContentLanguage()
                .HasContentMD5()
                .ResponseIsTextHtml()
                .Evaluate<bool>(true, string.Format(CultureInfo.InvariantCulture, "contains(//a[@id='location']/@href, '{0}')", location));
        }

        ITestHttp IRequestContent.ResponseHasNoContent()
        {
            this.Items.Add(new HttpContentTypeTest(null));
            this._response = NoContentResponse.New(this._request, this._content);
            return this;
        }

        ITestHttp IRequestContent.ResponseIsApplicationJson()
        {
            return (this as IRequestContent).ResponseIsApplicationJson(new ContentType("application/json"));
        }

        ITestHttp IRequestContent.ResponseIsApplicationJson(ContentType type)
        {
            this.Items.Add(new HttpContentTypeTest(type));
            this._response = TextPlainResponse.New(this._request, this._content);
            return this;
        }

        IResponseHtml IRequestContent.ResponseIsApplicationXhtml()
        {
            return (this as IRequestContent).ResponseIsApplicationXhtml(null);
        }

        IResponseHtml IRequestContent.ResponseIsApplicationXhtml(IResource resource)
        {
            return (this as IRequestContent).ResponseIsApplicationXhtml(resource, true);
        }

        IResponseHtml IRequestContent.ResponseIsApplicationXhtml(IResource resource, bool metaContentType)
        {
            (this as IRequestContent).ResponseIsHtml(new ContentType("application/xhtml+xml"), resource, metaContentType);
            return this;
        }

        IResponseXml IRequestContent.ResponseIsApplicationXml()
        {
            return (this as IRequestContent).ResponseIsApplicationXml(new ContentType("application/xml"));
        }

        IResponseXml IRequestContent.ResponseIsApplicationXml(ContentType type)
        {
            this.Items.Add(new HttpContentTypeTest(type));
            this._response = XmlResponse.New(this._request, this._content);
            return this;
        }

        IResponseHtml IRequestContent.ResponseIsHtml(ContentType value, IResource resource)
        {
            (this as IRequestContent).ResponseIsHtml(value, resource, true);
            return this;
        }

        IResponseHtml IRequestContent.ResponseIsHtml(ContentType value, IResource resource, bool metaContentType)
        {
            this.Items.Add(new HttpContentTypeTest(value));
            this._response = HtmlResponse.New(this._request, this._content);

            if (null != resource)
            {
                if (null != resource.Title)
                {
                    (this as IResponseHtml).Evaluate<string>(HttpUtility.HtmlEncode(resource.Title), "string(//h1/text())");
                    (this as IResponseHtml).EvaluateTrue("starts-with(/html/head/title/text(), '" + HttpUtility.HtmlEncode(resource.Title).Replace("'", "&apos;") + "')");
                }

                if (metaContentType)
                {
                    (this as IResponseHtml).Evaluate<string>(value.MediaType, "string(/html/head/meta[@http-equiv='Content-Type']/@content)");
                    (this as IResponseHtml).Evaluate<string>(value.MediaType, "string(/html/head/meta[@name='DC.format'][@scheme='IMT']/@content)");
                    (this as IResponseHtml).Evaluate<string>("Text", "string(/html/head/meta[@name='DC.type'][@scheme='DCMIType']/@content)");
                }

                if (null != resource.Metadata)
                {
                    foreach (var meta in resource.Metadata)
                    {
                        string xpath = "string(/html/head/meta";
                        if (!string.IsNullOrEmpty(meta.HttpEquivalent))
                        {
                            xpath += string.Format(CultureInfo.InvariantCulture, "[@http-equiv='{0}']", meta.HttpEquivalent);
                        }
                        else
                        {
                            xpath += string.Format(CultureInfo.InvariantCulture, "[@name='{0}']", meta.Name);
                            if (null == meta.Scheme)
                            {
                                xpath += "[not(@scheme)]";
                            }
                            else
                            {
                                xpath += string.Format(CultureInfo.InvariantCulture, "[@scheme='{0}']", meta.Scheme);
                            }
                        }

                        (this as IResponseHtml).Evaluate<string>(meta.Content, xpath + "/@content)");
                    }
                }

                if (null != resource.Links)
                {
                    foreach (var link in resource.Links)
                    {
                        string xpath = "string(/html/head/link";
                        if (!string.IsNullOrEmpty(link.Relationship))
                        {
                            xpath += string.Format(CultureInfo.InvariantCulture, "[@rel='{0}']", link.Relationship);
                        }
                        else if (!string.IsNullOrEmpty(link.ReverseLink))
                        {
                            xpath += string.Format(CultureInfo.InvariantCulture, "[@rev='{0}']", link.ReverseLink);
                        }
                        else
                        {
                            throw new TestException("All links must have either a rel or rev attribute.");
                        }

                        /// TODO : handle type='' and media='' attributes

                        (this as IResponseHtml).Evaluate<string>(link.Hyperlink.IsAbsoluteUri ? link.Hyperlink.AbsoluteUri : link.Hyperlink.OriginalString, xpath + "/@href)");
                    }
                }
            }

            return this;
        }

        ITestHttp IRequestContent.ResponseIsImageIcon()
        {
            this.Items.Add(new HttpContentTypeTest(new ContentType("image/x-icon")));
            this._response = BinaryResponse.New(this._request, this._content);
            return this;
        }

        IResponseHtml IRequestContent.ResponseIsTextHtml()
        {
            return (this as IRequestContent).ResponseIsTextHtml(null);
        }

        IResponseHtml IRequestContent.ResponseIsTextHtml(IResource resource)
        {
            return (this as IRequestContent).ResponseIsTextHtml(resource, true);
        }

        IResponseHtml IRequestContent.ResponseIsTextHtml(IResource resource, bool metaContentType)
        {
            (this as IRequestContent).ResponseIsHtml(new ContentType("text/html"), resource, metaContentType);
            return this;
        }

        ITestHttp IRequestContent.ResponseIsTextCss()
        {
            this.Items.Add(new HttpContentTypeTest(new ContentType("text/css")));
            this._response = TextPlainResponse.New(this._request, this._content);
            return this;
        }

        ITestHttp IRequestContent.ResponseIsTextJavaScript()
        {
            this.Items.Add(new HttpContentTypeTest(new ContentType("text/javascript")));
            this._response = TextPlainResponse.New(this._request, this._content);
            return this;
        }

        ITestHttp IRequestContent.ResponseIsTextPlain()
        {
            this.Items.Add(new HttpContentTypeTest(new ContentType("text/plain")));
            this._response = TextPlainResponse.New(this._request, this._content);
            return this;
        }

        ITestHttp IRequestContent.ResponseIs(ContentType type)
        {
            this.Items.Add(new HttpContentTypeTest(type));
            this._response = BinaryResponse.New(this._request, this._content);
            return this;
        }

        IResponseHtml IResponseHtml.EvaluateFalse(params string[] xpaths)
        {
            foreach (string xpath in xpaths)
            {
                this.Items.Add(new HtmlXpathEvaluateTest<bool>(false, xpath));
            }

            return this;
        }

        IResponseHtml IResponseHtml.EvaluateTrue(params string[] xpaths)
        {
            foreach (string xpath in xpaths)
            {
                this.Items.Add(new HtmlXpathEvaluateTest<bool>(true, xpath));
            }

            return this;
        }

        IResponseHtml IResponseHtml.Evaluate<T>(T expected, params string[] xpaths)
        {
            foreach (string xpath in xpaths)
            {
                this.Items.Add(new HtmlXpathEvaluateTest<T>(expected, xpath));
            }

            return this;
        }

        IResponseHtml IResponseHtml.HasRobotsTag(string value)
        {
            (this as IResponseHtml).Evaluate<string>(value, "string(/html/head/meta[@name='robots']/@content)");

            return this;
        }

        IResponseHtml IResponseHtml.HasStyleSheetLink(string href)
        {
            string xpath = string.Format(CultureInfo.InvariantCulture, "count(/html/head/link[@rel='stylesheet'][@type='text/css'][@href='{0}'])", href);
            (this as IResponseHtml).Evaluate<double>(1, xpath);

            return this;
        }

        IResponseXml IResponseXml.EvaluateFalse(params string[] xpaths)
        {
            foreach (string xpath in xpaths)
            {
                this.Items.Add(new XmlXpathEvaluateTest<bool>(false, xpath));
            }

            return this;
        }

        IResponseXml IResponseXml.EvaluateTrue(params string[] xpaths)
        {
            foreach (string xpath in xpaths)
            {
                this.Items.Add(new XmlXpathEvaluateTest<bool>(true, xpath));
            }

            return this;
        }

        IResponseXml IResponseXml.Evaluate<T>(T expected, params string[] xpaths)
        {
            foreach (string xpath in xpaths)
            {
                this.Items.Add(new XmlXpathEvaluateTest<T>(expected, xpath));
            }

            return this;
        }

        IResponseXml IResponseXml.Evaluate<T>(T expected, string xpath, params XmlNamespace[] namespaces)
        {
            this.Items.Add(new XmlXpathEvaluateTest<T>(expected, xpath, namespaces));
            return this;
        }

        ITestHttp ITestHttp.HasContentLocation(AbsoluteUri location)
        {
            this.Items.Add(new HttpResponseContentLocationTest(location));
            return this;
        }

        ITestHttp ITestHttp.DoesNotHaveCookie(string name)
        {
            this.Items.Add(new HttpCookieTest(false, name));
            return this;
        }

        ITestHttp ITestHttp.HasCookie(string name)
        {
            (this as ITestHttp).HasCookie(name, null);
            return this;
        }

        ITestHttp ITestHttp.HasCookie(string name, string value)
        {
            this.Items.Add(new HttpCookieTest(true, name, value));
            return this;
        }

        ITestHttp ITestHttp.HasNoCookies()
        {
            this.Items.Add(new HttpCookielessTest());
            return this;
        }

        private Uri AppendToPath(string value)
        {
            string uri = ((Uri)this._request.Uri).AbsoluteUri;
            string query = ((Uri)this._request.Uri).Query;
            return new Uri(string.Concat(uri.Substring(0, uri.Length - query.Length), value, query));
        }
    }
}